<template>
	<view class="page" :renderdata="renderdata" :change:renderdata="ether.changedatastatus" :beforedata="beforedata"
		:change:beforedata="ether.beforedatachange">
		<HeaderBar :title="title" showBack fixed />
		<web-view :src="src" @message="getMessage" :getdata="getdata" :change:getdata="ether.zhixing"
			:fullscreen="false" :webview-styles="webstyles"></web-view>
	</view>
</template>
<script>
	import common from '@/js_sdk/common/common.js'
	var this_, subNVue;
	export default {
		data() {
			return {
				// 当前显示的页面链接
				src: "",
				// 当前的webView对象
				webView: '',
				//  用于驱动获取弹窗页的数据
				beforedata: null,
				// 用于驱动执行的数据
				getdata: null,
				// webview的样式
				webstyles: {
					top: "88px"
				},
				// 页面的标题
				title: ""
			}
		},
		computed: {
			// renderjs层需要的信息
			renderdata() {
				return {
					privateKey: this.$store.getters.addressInfo.privateKey,
					fullHost: this.$store.getters.getRpc,
					address: this.$store.getters.addressInfo.address,
					config: common.config,
					src: this.src
				}
			}
		},
		onLoad(option) {
		    this_ = this
			// 获取屏幕的高度,来定义webview的位置和高度
			uni.getSystemInfo({
				success: function(res) {
					let height = res.windowHeight - 88
					this_.webstyles.height = height + "px"
				}
			});
			this.src = option.url
			this.search(this.src)
		},
		async mounted() {
			// 这个showloading是配合tron对象注入的，在注入成功前不想让用户操作
			// 会显示很短的时间
			uni.showLoading({
				title: "加载中",
				mask: true,
			})
			subNVue = uni.getSubNVueById('tronconcat')
			subNVue.setStyle({
				position: "absolute", //除 popup 外，其他值域参考 5+ webview position 文档
				height: "100%",
				width: "100%",
				background: "transparent",
				bottom: "0px"
			})
			// 这里很坑，不隐藏的话好像默认是显示状态
			subNVue.hide()
			// 给webview中注入JS，应为webview刚开始获取不到，所以这里用了延迟执行来解决
			this.setInterval()
		},
		methods: {
			// 获取网页标题的方法
			search(res) {
				let cleanUrl = res.split('?')[0]; // 移除查询参数
				cleanUrl = cleanUrl.split('#')[0]; // 移除哈希部分
				uni.request({
					url: cleanUrl,
					method: 'GET',
					success: (response) => {
						const htmlText = response.data;
						// 使用正则表达式提取标题
						const titleMatch = htmlText.match(/<title[^>]*>([^<]+)<\/title>/);
						const title = titleMatch ? titleMatch[1] : 'No Title';
						this.title = title
					},
					fail: (error) => {
						console.error('Error fetching the URL:', error);
					}
				});
			},
			// 定时给webview注入JS的方法
			setInterval() {
				let currentWebview = this_.$scope.$getAppWebview();
				this_.webView = currentWebview.children()[1];
				if (this_.webView) {
					this_.webView.onloaded = function(e) {
						this_.webView.appendJsFile("static/script/uni.js");
						this_.webView.appendJsFile("static/script/newTronWeb.js");
						this_.webView.appendJsFile("static/script/tron2.js");
						let evalJS = `
								init('${this_.renderdata.fullHost}','${this_.renderdata.address}')
							`
						this_.webView.evalJS(evalJS);
					}
				} else {
					setTimeout(this_.setInterval, 100)
				}
			},
			// 获取Dapp传过来的数据
			async getMessage(e) {
				let newdata2 = e.detail.data[0]
				// TRON对象注入成功后隐藏加载框
				if (newdata2.handler == "hideloading") {
					uni.hideLoading()
				} else if (newdata2.propKey == "trx" && newdata2.trxPropKey == "sign") {
					// 经测试如果是调用合约的话，args中会有两个数据
					// 第一个是要签名的数据
					// 第二个是签名使用的私钥，我们在DAPP中没有注入私钥，所以这里的私钥肯定是false；
					// 我尝试过直接签名，提示私钥不匹配，这说明这里的私钥还是要验证的，所以签名前必须修改私钥

					// 这里还有一种情况就是构造好TRX交易而后进行签名，这里面有raw_data，但是没有私钥。
					// 所以这种情况也不需要修改私钥，这里进行下说明
					if (newdata2.args && newdata2.args[0].raw_data && newdata2.args[1] == false) {
						newdata2.args[1] = this.renderdata.privateKey
					}
					// 在APP中修改了私钥以后再跑后面的逻辑，就不可能泄露私钥了
					this.beforedata = newdata2
				} else if (newdata2.propKey == "trx" && newdata2.trxPropKey == "sendTransaction") {
					this_.beforedata = newdata2
				} else if (newdata2.propKey == "trx" && newdata2.trxPropKey == "signMessageV2") {
					this_.beforedata = newdata2
				} else {
					this_.getdata = newdata2
				}
			},
			// 打开底部弹窗
			async opendibu(data) {
				uni.hideLoading()
				subNVue.show('slide-in-bottom', 200, () => {
					uni.$emit("openconcat", data.res)
				})
				// 获取执行结果
				let returnstatus = await this.awaitretrundata(data.res.id)
				if (returnstatus) {
					// 弹窗页面其实只会返回执行与不执行，执行的话就修改getdata，驱动执行方法
					this.getdata = data.newvalue
				} else {
					// 不执行的话就让DAPP触发一个报错，这里主要是生成参数
					let errordata = {
						message: "Confirmation declined by user",
						newvalue: data.newvalue
					}
					this.returnerror(errordata)
				}
			},
			// 等待页面同意或拒绝的方法
			awaitretrundata(id) {
				return new Promise((resolve, reject) => {
					uni.$on("returndata", function(e) {
						if (e.id == id) {
							subNVue.hide()
							resolve(e.status);
						}
					})
				})
			},
			// rendjs层执行完后通过这个方法把结果返回给DAPP。
			// 这里的data处理了一下，所以肯定是对象，所以把字符串的情况删掉了
			// 这里跟ETH是有区别的，ETH是有封装好的方法我们来调用
			// 而这里的方法是我们自己封装的，肯定是对象
			returndata(data) {
				let newdata = JSON.stringify(data)
				let evalJS = `
						methodB(${newdata})
					`
				this_.webView.evalJS(evalJS);
			},
			// rendjs层开始处理弹窗页的数据时，清空beforedata的值
			initbeforedata() {
				this.beforedata = null
			},
			//rendjs层开始执行DAPP的逻辑时，清空 getdata的值
			initgetdata() {
				this.getdata = null
			}
		}
	}
</script>
<script module="ether" lang="renderjs">
	var this_, tronWeb;
	export default {
		data() {
			return {
				// JS注入的状态
				loadstatus: false,
				// 数据同步的状态
				datastatus: false,
				// 私钥
				privateKey: null,
				// RPC节点
				fullHost: null,
				// 页面路由
				src: null,
				// 配置相关的数据
				config: null,
				// 地址
				address: null

			}
		},
		mounted() {
			this_ = this
			let script = document.createElement('script')
			script.src = 'static/script/newTronWeb.js'
			script.onload = this.changeloadstatus
			document.head.appendChild(script)
			let script2 = document.createElement('script')
			script2.src = 'static/script/ethers-5.2.umd.min.js'
			document.head.appendChild(script2)
		},
		methods: {
			// JS 文件引入成功后修改引入状态
			changeloadstatus() {
				this.loadstatus = true
				if (this.datastatus == true) {
					this.inittronWeb()
				}
			},
			// 私钥和rpc更新过来后修改数据状态
			// 这样处理的原因是因为两者执行顺序不确定
			// 所有两者都准备好后，由后准备好的数据来触发构建节点方法
			changedatastatus(newValue) {
				if (newValue && newValue != undefined) {
					this.privateKey = newValue.privateKey
					this.fullHost = newValue.fullHost
					this.config = newValue.config,
						this.src = newValue.src
					this.address = newValue.address
					this.datastatus = true
					if (this.loadstatus == true) {
						this.inittronWeb()
					}
				}
			},
			// 这里很奇怪，我发现不配置key完全不影响使用，相反配置了反而报错
			inittronWeb() {
				tronWeb = new TronWeb.TronWeb({
					fullHost: this.fullHost,
					privateKey: this.privateKey
				})
			},
			// 我们是通过ETH的包来解析的数据，返回结果中会有BigNumber
			// 而BigNumber不能作为参数在波场中使用，所以需要转换成字符串
			changedata(data) {
				let res = data.map(item => {
					if (typeof item === 'object' && item !== null) {
						if (ethers.BigNumber.isBigNumber(item)) {
							return item.toString(); // 替换hex
						} else {
							return this_.changedata(item)
						}
					} else {
						return item; // 保留其他类型的原始值
					}
				})
				return res
			},
			async beforedatachange(newvalue) {
				if (newvalue) {
					this.$ownerInstance.callMethod('showloading')
					try {
						this.$ownerInstance.callMethod('initbeforedata')
						let res;
						// || newvalue.trxPropKey == "signMessageV2"
						if (newvalue.trxPropKey == "sign"||newvalue.trxPropKey == "signMessageV2") {
							// 签名方法中有两种情况
							// 一种是给请求合约或构建好的TRX交易进行签名
							// 另一种是给普通数据进行签名

							// 这里先处理给合约进行签名的的情况
							if (newvalue.args[0] && newvalue.args[0].raw_data) {
								// 这里有个很恶心的情况
								// TRX转账有两种方式,第一种就是直接转账的方法，直接使用sendTransaction。
								// 第二种是构造一笔转账，然后签名进行转账，与合约一样但是参数内容不一致
								// 所以这里我们要分为两种情况去处理
								// 这里因为层级太深，所以合并一下
								let value = newvalue.args[0].raw_data.contract[0].parameter.value

								// 合约转账中，value中必然包含contract_address和data
								if (value.contract_address && value.data) {
									// 用abi定义一个合约的接口实例

									// 与ETH不同，TRON链预估手续费需要知道调用用的方法和对应的abi，光有data不行
									// 所以我们先构建一个合约来获取abi
									let contract = await tronWeb.contract().at(value.contract_address)
									// data的0-8位，是方法id，这里是不加"0x"的情况
									let functionSignature = value.data.slice(0, 8);
									// 将合约的所有属性名存在一个数组中
									let arr = Object.keys(contract)
									// 方法id是将方法名通过sha3加密后，进行截取获得的
									// 这里我们将合约的所有方法都sha3一下，然后进行对比获取到对应的方法名
									// 这个方法名携带参数类型
									let methodname2;
									arr.forEach(function(item) {
										if (tronWeb.sha3(item).slice(2, 10) == functionSignature) {
											methodname2 = item
											return
										}
									})
									// 合约中可以通过methodInstances用方法ID获取到方法对应的ABI
									let abi = contract.methodInstances[functionSignature].abi
									// 而后我们以来ether的接口对象，来解析数据
									let parsing = new ethers.utils.Interface([abi])
									let parsingres = parsing.parseTransaction({
										data: "0x" + value.data
									})
									// 这个methodname与methondname2不同，只有方法名
									// 不携带参数类型
									let methodname = parsingres.name
									// 因为ether.js解析出来的数据有bigNumber，这里需要换成String
									let methodargs = this_.changedata(parsingres.args)
									let inputs = parsingres.functionFragment.inputs
									// 将参数与参数类型拼接，得到我们预估手续费时需要的参数
									let inputargs = []
									methodargs.forEach(function(item, index) {
										let inputarg = {
											type: inputs[index].type,
											value: item
										}
										inputargs.push(inputarg)
									})
									// 这里处理请求合约时携带TRX的情况
									let option = {}
									if (value.call_value) {
										option.callValue = value.call_value
									}
									// 预估能量消耗和费用
									let Gaslimit = await tronWeb.transactionBuilder.triggerConstantContract(value
										.contract_address,
										methodname2, option,
										inputargs, tronWeb.defaultAddress.hex);
									let energy = Gaslimit.energy_used
									let energycost = energy * this.config.energprice
									// 预估带宽消耗
									let bandwidth = Gaslimit.transaction.raw_data_hex.length + 2
									let bandcost = bandwidth * this.config.bandwidthprice
									// 计算总费用
									let allcost = bandcost + energycost

									// 至此核心的手续费预估功能就已经完成了

									// 下面是针对转账和授权两个常用方法的处理
									if (methodname == "transfer" || methodname == "approve") {
										// 构建合约来获取代币的小数位和符号
										// 这里有个前提就是调用这两个方法的话就一定是个代币合约
										let symbol = await contract.symbol().call()
										let decimals = await contract.decimals().call()
										res = {
											from: tronWeb.address.fromHex(value.owner_address),
											to: tronWeb.address.fromHex(methodargs[0]),
											value: ethers.utils.formatUnits(methodargs[1].toString(), decimals),
											symbol: symbol,
											fee: tronWeb.fromSun(allcost),
											methodname: methodname,
											id: newvalue.id,
											handler: newvalue.trxPropKey,
											src: this.src
										}
									} else {
										// 处理其他不常用的方法
										res = {
											from: tronWeb.address.fromHex(value.owner_address),
											to: tronWeb.address.fromHex(value.contract_address),
											symbol: "TRX",
											fee: tronWeb.fromSun(allcost),
											methodname: "other",
											id: newvalue.id,
											handler: newvalue.trxPropKey,
											src: this.src,
											value: 0
										}
										// 经测试调用合约时如果要串TRX，数量存储在call_value中
										if (value.call_value) {
											let val = value.call_value
											res.value = tronWeb.fromSun(val)
										}

									}
								}
								// 经测试，构建的TRX交易与合约的区别有两点
								// 1.没有合约地址，只有to地址
								// 2.没有data,只有amount
								// 所以判断段条件如下
								else if (value.to_address && value.amount) {
									let daikuan = newvalue.args[0].raw_data_hex.length + 2
									let daikuancost = daikuan * this.config.bandwidthprice
									res = {
										from: tronWeb.address.fromHex(value.owner_address),
										to: tronWeb.address.fromHex(value.to_address),
										symbol: "TRX",
										value: tronWeb.fromSun(value
											.amount),
										fee: tronWeb.fromSun(daikuancost),
										methodname: "sendTransaction",
										id: newvalue.id,
										handler: newvalue.trxPropKey,
										src: this.src
									}
								}

							}
							// 这里是处理普通签名的情况
							else {
								res = {
									data: newvalue.args[0],
									address: this.address,
									id: newvalue.id,
									handler: newvalue.trxPropKey,
									methodname: "",
								}
							}

						} else if (newvalue.trxPropKey == "sendTransaction") {
							let transaction = await tronWeb.transactionBuilder.sendTrx(newvalue.args[0], newvalue.args[
									1],
								this.address);
							let daikuan = transaction.raw_data_hex.length + 2
							let daikuancost = daikuan * this.config.bandwidthprice
							res = {
								from: this.address,
								to: newvalue.args[0],
								value: tronWeb.fromSun(newvalue.args[1]),
								fee: tronWeb.fromSun(daikuancost),
								id: newvalue.id,
								handler: newvalue.trxPropKey
							}
						}

						this.$ownerInstance.callMethod('opendibu', {
							res,
							newvalue
						})
					} catch (e) {
						let postdata = {
							data: String(e.message),
							id: newvalue.id,
							status: false
						}
						this_.$ownerInstance.callMethod('returndata', postdata)
						//TODO handle the exception
					}
					this.$ownerInstance.callMethod('hideloading')
				}
			},
			// 钱包接收DAPP的所有请求都会在这里执行。
			async zhixing(newValue) {
				if (newValue && newValue != undefined) {
					this.$ownerInstance.callMethod('showloading')
					try {
						this.$ownerInstance.callMethod('initgetdata')
						let getdata = newValue
						// 执行那两个对象对应的方法
						let postdata;
						if (getdata.type == "function") {
							let data = await tronWeb[getdata.propKey][getdata.trxPropKey](...getdata.args)
							postdata = {
								data: data,
								id: getdata.id,
								status: true
							}
						} else {
							let test = await tronWeb[getdata.propKey][getdata.trxPropKey]
							postdata = {
								data: test,
								id: getdata.id,
								status: true
							}
						}
						this_.$ownerInstance.callMethod('returndata', postdata)
					} catch (e) {
						let postdata = {
							data: String(e.message),
							id: getdata.id,
							status: false
						}
						this_.$ownerInstance.callMethod('returndata', postdata)
						//TODO handle the exception
					}
					this.$ownerInstance.callMethod('hideloading')
				}
			},

		}
	}
</script>

<style lang='less' scoped>
	.page {
		box-sizing: border-box;
		width: 100vw;
		height: 100vh;
		display: flex;
		flex-direction: column;
		background-color: #F6F6F6;

		.tanchuang {
			width: 100vw;
			height: 50vh;
			background-color: #000;
			position: absolute;
			left: 0px;
			bottom: 0px;
			right: 0px;
		}
	}
</style>